package com.wei.service.wrapper;

import com.wei.utils.Assert;
import org.redisson.RedissonMultiLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.concurrent.TimeUnit;

/**
 * Redisson分布式事务锁实现
 * 在Redis的分布式环境中，我们假设有N个Redis master。这些节点完全互相独立，不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。现在我们假设有5个Redis master节点，同时我们需要在5台服务器上面运行这些Redis实例，这样保证他们不会同时都宕掉。
 * 为了取到锁，客户端应该执行以下操作:
 * 获取当前Unix时间，以毫秒为单位。
 * 依次尝试从5个实例，使用相同的key和具有唯一性的value（例如UUID）获取锁。当向Redis请求获取锁时，客户端应该设置一个网络连接和响应超时时间，这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒，则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下，客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应，客户端应该尽快尝试去另外一个Redis实例请求获取锁。
 * 客户端使用当前时间减去开始获取锁时间（步骤1记录的时间）就得到获取锁使用的时间。当且仅当从大多数（N/2+1，这里是3个节点）的Redis节点都取到锁，并且使用的时间小于锁失效时间时，锁才算获取成功。
 * 如果取到了锁，key的真正有效时间等于有效时间减去获取锁所使用的时间（步骤3计算的结果）。
 * 如果因为某些原因，获取锁失败（没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间），客户端应该在所有的Redis实例上进行解锁（即便某些Redis实例根本就没有加锁成功，防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁）。
 */
public class RedissonLocker implements Locker {
    private static final Logger logger = LoggerFactory.getLogger(RedissonLocker.class);

    private static final String MESSAGE = "获取锁对象为空！";

    @Autowired
    private RedissonClient redissonClient;


    public RedissonLocker() {
    }

    @Override
    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        Assert.notNull(lock, MESSAGE);
        lock.lock();
    }

    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        Assert.notNull(lock, MESSAGE);
        lock.unlock();
    }

    @Override
    public void lock(String lockKey, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        Assert.notNull(lock, MESSAGE);
        lock.lock(leaseTime, TimeUnit.SECONDS);
    }

    @Override
    public void lock(String lockKey, TimeUnit unit, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        Assert.notNull(lock, MESSAGE);
        lock.lock(timeout, unit);
    }

    @Override
    public boolean tryLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        Assert.notNull(lock, MESSAGE);
        return lock.tryLock();
    }

    @Override
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (Exception e) {
            logger.error("获取锁异常", e);
            return false;
        }
    }

    /**
     * 尝试获取锁，在等待时间内获取到锁则返回true,否则返回false,如果获取到锁，则要么执行完后程序释放锁， 要么在给定的超时时间leaseTime后释放锁
     *
     * @param lockKey
     * @param waitTime
     * @param unit
     * @return
     */
    @Override
    public boolean tryLock(String lockKey, long waitTime, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, unit);
        } catch (Exception e) {
            logger.error("获取锁异常", e);
            return false;
        }
    }

    @Override
    public boolean isLocked(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        Assert.notNull(lock, MESSAGE);
        return lock.isLocked();
    }

    /**
     * 组锁,锁住lockKey
     * 将多个RLock对象分组，并将它们作为一个锁处理
     *
     * @param lockKey
     * @return
     */
    @Override
    public RedissonMultiLock multiLock(String... lockKey) {
        RLock[] rLocks = new RLock[lockKey.length];
        for (int i = 0; i < lockKey.length; i++) {
            rLocks[i] = redissonClient.getLock(lockKey[i]);
        }
        RedissonMultiLock lock = new RedissonMultiLock(rLocks);
        Assert.notNull(lock, MESSAGE);
        lock.lock();
        return lock;
    }

    /**
     * 解组锁
     *
     * @param lock
     */
    @Override
    public void unlock(RedissonMultiLock lock) {
        lock.unlock();
    }

    /**
     * 组锁,锁住lockKey
     * 将多个RLock对象分组，并将它们作为一个锁处理
     *
     * @param lockKeys
     * @return
     */
    @Override
    public RedissonMultiLock multiLock(long timeout, String... lockKeys) {
        RLock[] rLocks = new RLock[lockKeys.length];
        for (int i = 0; i < lockKeys.length; i++) {
            rLocks[i] = redissonClient.getLock(lockKeys[i]);
        }
        RedissonMultiLock lock = new RedissonMultiLock(rLocks);
        Assert.notNull(lock, MESSAGE);
        lock.lock(timeout, TimeUnit.SECONDS);
        return lock;
    }

    /**
     * @param timeout
     * @param unit
     * @param lockKeys
     * @return
     */
    @Override
    public RedissonMultiLock tryMultiLock(long timeout, TimeUnit unit, String... lockKeys) {
        RLock[] rLocks = new RLock[lockKeys.length];
        for (int i = 0; i < lockKeys.length; i++) {
            rLocks[i] = redissonClient.getLock(lockKeys[i]);
        }
        RedissonMultiLock lock = new RedissonMultiLock(rLocks);
        try {
            if (lock.tryLock(timeout, TimeUnit.SECONDS)) {
                return lock;
            }
        } catch (Exception e) {
            logger.error("获取联合锁异常", e);
            return null;
        }
        return null;
    }

}
